/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_orm.htm
 *
 * Zhiqim Orm is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.orm.dbo.defined;

import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;

import org.zhiqim.kernel.model.maps.HashMapSS;
import org.zhiqim.kernel.model.maps.HashMapSV;
import org.zhiqim.kernel.model.maps.LinkedMapSV;
import org.zhiqim.kernel.model.maps.MapSO;
import org.zhiqim.kernel.model.maps.MapSS;
import org.zhiqim.kernel.model.maps.MapSV;
import org.zhiqim.kernel.util.Arrays;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Lists;
import org.zhiqim.kernel.util.Replaces;
import org.zhiqim.kernel.util.Validates;
import org.zhiqim.orm.ORMConstants;
import org.zhiqim.orm.ORMException;
import org.zhiqim.orm.ZTable;
import org.zhiqim.orm.dbo.Dbo;
import org.zhiqim.orm.dbo.Selector;

/**
 * 视图对象，配置格式如：<br><br>
 * <view name="com.zhiqim.dbo_ex.UserEX" table="USER, USER_EX"><br>
 *     <field name="userId" table="USER" column="USER_ID" type="long"/><br>
 *     <field name="userName" table="USER" column="USER_NAME" type="String"/><br>
 *     <field name="userDesc" table="USER_EX" column="USER_DESC" type="String"/><br>
 *     <join type="EQUAL" lTable="USER" lColumn="USER_ID" rTable="USER_EX" rColumn="USER_ID"/><br>
 * </view><br><br>
 * 自联表注意使用别名配置，如下配置格式：
 * <view name="com.zhiqim.dbo_ex.UserEX" table="USER u, USER e"><br>
 *     <field name="userId" table="u" column="USER_ID" type="long"/><br>
 *     <field name="userName" table="u" column="USER_NAME" type="String"/><br>
 *     <field name="userDesc" table="e" column="USER_DESC" type="String"/><br>
 *     <join type="EQUAL" lTable="u" lColumn="USER_ID" rTable="e" rColumn="USER_ID"/><br>
 * </view><br><br>
 * 用于ZView时，传入_View类，映射成数据库多表，和根据数据库表列组装成_View对象
 * 
 * 注：_View不支持通过column找到_ViewField
 *
 * @version v1.0.0 @author zouzhigang 2014-3-21 新建与整理
 */
public class _View implements ORMConstants, Dbo
{
    private String name;
    private String table;
    
    private LinkedMapSV<_ViewField> fieldMap = new LinkedMapSV<>();
    private List<_ViewJoin> joinList = new ArrayList<>();
    
    public _View(String name, String table)
    {
        this.name = name;
        this.table = table;
    }
    
    public void addField(String name, String table, String column, int type)
    {
        _ViewField field = new _ViewField(name, table, column.toUpperCase(), type);
        fieldMap.put(name, field);
    }
    
    public void addJoin(String type, String lTable, String lColumn, String rTable, String rColumn, String rValue)
    {
        if (Validates.isNotEmpty(rColumn))
            rColumn = rColumn.toUpperCase();
        
        _ViewJoin join = new _ViewJoin(type, lTable, lColumn.toUpperCase(), rTable, rColumn, rValue);
        joinList.add(join);
    }
    
    @Override
    public String toString()
    {
        StringBuilder strb = new StringBuilder(_FOUR_).append("<view")
            .append(" name=").append(_DOUBLE_QUOTE_).append(name).append(_DOUBLE_QUOTE_)
            .append(" table=").append(_DOUBLE_QUOTE_).append(table).append(_DOUBLE_QUOTE_)
            .append(">").append(_BR_);
        
        for (_ViewField field : fieldMap.values())
        {
            strb.append(_FOUR_).append(_FOUR_).append(field).append(_BR_);
        }
        
        strb.append(_BR_);
        for (_ViewJoin where : joinList)
        {
            strb.append(_FOUR_).append(_FOUR_).append(where).append(_BR_);
        }
        
        strb.append(_FOUR_).append("</view>");
        return strb.toString();
    }
    
    /***************************************************************************************************/
    //DBO接口实现的方法
    /***************************************************************************************************/
    
    @Override /** 获取类名 */
    public String getName()
    {
        return name;
    }
    
    @Override /** 判断是否存在该字段 */
    public boolean hasField(String fieldName)
    {
        return fieldMap.containsKey(fieldName);
    }
    
    @Override /** 通过字段名获取字段 */
    public _ViewField getField(String fieldName)
    {
        return fieldMap.get(fieldName);
    }
    
    @Override /** 通过字段名获取组装GET方法 */
    public String getGetMethod(String fieldName)
    {
        _ViewField field = fieldMap.get(fieldName);
        return (field == null)?null:field.getGetMethod();
    }
    
    @Override /** 通过字段获取数据库列名 */
    public String getColumn(String fieldName)
    {
        _ViewField field = fieldMap.get(fieldName);
        return (field == null)?null:(field.getTable() + "." + field.getColumn());
    }
    
    @Override /** 获取字段列表按长度排序 */
    public _ViewField[] getFieldColumnListOrderByLen()
    {
        _ViewField[] array = new _ViewField[fieldMap.size()];
        int index = 0;
        for (_ViewField field : fieldMap.values())
        {
            array[index++] = field;
        }
        
        for (int i=0;i<array.length;i++)
        {
            for (int j=i+1;j<array.length;j++)
            {
                if (array[i].getField().length() < array[j].getField().length())
                {//冒泡交换
                    _ViewField v = array[i];
                    array[i] = array[j];
                    array[j] = v;
                }
            }
        }
        
        return array;
    }
    
    /***************************************************************************************************/
    //_View私有的方法
    /***************************************************************************************************/
    
    /** 是否有对应的所有字段名 */
    public boolean hasAllField(String[] fieldArr)
    {
        for (String field : fieldArr)
        {
            if (!fieldMap.containsKey(field))
                return false;
        }
        return true;
    }
    
    public String[] getTableArr()
    {
        return Arrays.toStringArray(table, ",");
    }
    
    public List<String> getTableNoAliasList()
    {
        ArrayList<String> list = new ArrayList<>();
        
        String[] tableArr = getTableArr();
        for (String table : tableArr)
        {
            String[] alias = Arrays.toStringArray(table, " ");
            if (alias.length == 2)
                list.add(alias[0]);
            else
                list.add(table);
        }
        
        return Lists.trim(list);
    }
    
    /** 根据名称查字段set方法 */
    public String getSetMethod(String name)
    {
        _ViewField field = fieldMap.get(name);
        if (field == null)
            return null;
        
        return field.getSetMethod();
    }
    
    /** 可能表名有别名，再找一次 */
    public String getTableName(String tableName)
    {
        String[] tableArr = getTableArr();
        for (String table : tableArr)
        {
            String[] alias = Arrays.toStringArray(table, " ");
            if (alias.length != 2)
                continue;
            
            if (tableName.equals(alias[1]))
                return alias[0];
        }
        
        return tableName;
    }
    
    public List<_ViewField> getFieldList(String fields)
    {
        if (Validates.isEmptyBlank(fields))//为空表示全部
            return new ArrayList<_ViewField>(fieldMap.values());
        
        List<String> fieldNameList = Lists.toStringList(fields);
        List<_ViewField> fieldList = new ArrayList<_ViewField>();
        for (_ViewField field : fieldMap.values())
        {
            if (fieldNameList.contains(field.getField()))
                fieldList.add(field);
        }
        
        Asserts.as(!fieldList.isEmpty()?null:"["+fields+"]未找到一个匹配的字段");
        return fieldList;
    }
    
    /** 缺省字段 */
    public String getDefaultFieldSQL(String fields)
    {
        StringBuilder strb = new StringBuilder();
        
        List<_ViewField> fieldList = getFieldList(fields);
        //第一个
        _ViewField field = fieldList.get(0);
        strb.append(field.getTable()).append(".").append(field.getColumn()).append(" as ").append(field.getField());
        //剩下的前加逗号
        for (int i=1;i<fieldList.size();i++)
        {
            field = fieldList.get(i);
            strb.append(", ").append(field.getTable()).append(".").append(field.getColumn()).append(" as ").append(field.getField());
        }
        
        return strb.toString();
    }
    
    /** 连接表 */
    public String getJoinTable(ZTable zTable, Selector selector, MapSO paramMap) throws ORMException
    {
        //1.定义两个表，tableSQL用于把BUS_USER a, BUS_USER b中的b左右连替换成BUS_USER a left join BUS_USER b的风格
        MapSS tableSQL = new HashMapSS();
        MapSV<_ViewTableName> tableMap = new HashMapSV<>();
        
        String[] tableArray = getTableArr();
        for (String tableFull : tableArray)
        {
            int ind = tableFull.indexOf(" ");
            if (ind == -1)
            {
                String tableName = tableFull;
                String tableAlias = tableFull;
                tableSQL.put(tableAlias, tableFull);
                tableMap.put(tableAlias, new _ViewTableName(tableFull, tableName, tableAlias));
            }
            else
            {
                String tableName = tableFull.substring(0, ind);
                String tableAlias = tableFull.substring(ind+1).trim();
                tableSQL.put(tableAlias, tableFull);
                tableMap.put(tableAlias, new _ViewTableName(tableFull, tableName, tableAlias));
            }
        }
        
        //2.找到左右联的表，组装左右表对应的 join 和 on
        MapSS joinMap = new HashMapSS();
        
        for (_ViewJoin join : joinList)
        {
            String joinType = JOIN_LEFT.equals(join.getType())?" left join ":JOIN_RIGHT.equals(join.getType())?" right join ":" inner join ";
            String lTable = join.getLTable();
            String rTable = join.getRTable();
            
            _ViewTableName lTableName = tableMap.get(lTable);
            _ViewTableName rTableName = tableMap.get(rTable);
            
            //如果有别名，传入别名，或者传入表类名，得到带条件的join表
            String rTableFull = selector.getJoinWhereSQL(zTable, rTableName, paramMap);
            
            StringBuilder joinSql = new StringBuilder();
            
            if (joinMap.containsKey(lTable + "," + rTable))
            {//表示已有一个左右联，这里要求相同表联时，左右联一致
                joinSql.append(joinMap.get(lTable + "," + rTable)).append(" and ");
            }
            else
            {//新的连接，左全表名如BUS_USER a left join BUS_USER b on 或BUS_USER a left join (select USER_ID from BUS_USER) b on
                joinSql.append(lTableName.getTableFull()).append(joinType).append(rTableFull).append(" on ");
            }

            joinSql.append(join.getLTable()).append(".").append(join.getLColumn());
            joinSql.append(" = ");
            
            if (Validates.isEmptyBlank(join.getRValue()))
                joinSql.append(join.getRTable()).append(".").append(join.getRColumn()).append(" ");
            else
                joinSql.append("'").append(join.getRValue()).append("' ");
            
            joinMap.put(lTable + "," + rTable, joinSql.toString());
        }

        //3.去除右表，把左表替换成（左表 join 右表 on）
        for (Entry<String, String> entry : joinMap.entrySet())
        {
            String tableKey = entry.getKey();
            String joinSQL = entry.getValue();
            
            String[] tableArr = tableKey.split(",");
            String lTable = tableArr[0];
            String rTable = tableArr[1];
            
            String lTableValue = tableSQL.get(lTable);
            if (lTableValue == null)
                throw new ORMException("暂不支持嵌套左右连接,请调整为以一张表为主表进行左右连接");
            
            //3.1 去除右表
            tableSQL.remove(rTable);//，把左表替换成（左表 join 右表 on）
            
            //3.2 把左表替换成（左表 join 右表 on），左表有别名，则连别名一起替换，但里面的条件不能替换，因此先作转换，替换后转换回来
            String lTableFull = tableMap.get(lTable).getTableFull();
            lTableValue = Replaces.replaceAllEscape(lTableValue, lTable+".", "T1A2B3L4E5.");
            lTableValue = Replaces.replaceAllEscape(lTableValue, lTableFull, joinSQL);
            lTableValue = Replaces.replaceAllEscape(lTableValue, "T1A2B3L4E5.", lTable+".");
            tableSQL.put(lTable, lTableValue);
        }

        //4.组装最终joinSQL
        StringBuilder strb = new StringBuilder();
        for (String tab : tableSQL.values()){
            strb.append(tab).append(",");
        }
        if (strb.length() > 0){
            strb.setLength(strb.length()-1);
        }
        return strb.toString();
    }
    
    /** 检查表可替换参数，把对象中可替换字段转到替换表中 */
    public void chkTableReplace(ZTable zTable, MapSS replaceMap)
    {
        List<String> tableList = getTableNoAliasList();
        for (String table : tableList)
        {
            _Table _table = zTable.getTableByTableName(table);
            _table.chkReplace(replaceMap);
        }
    }
    
    /** 检查可替换参数，把替换表转到对象字段中 */
    public void addReplaceToData(ZTable zTable, Object data, MapSS replaceMap)
    {
        List<String> tableList = getTableNoAliasList();
        for (String table : tableList)
        {
            _Table _table = zTable.getTableByTableName(table);
            _table.addReplaceToData(data, replaceMap);
        }
    }
}
